/*
 *
 */
package com.mechanatrix.input;

import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.JoyAxisTrigger;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.input.controls.Trigger;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import java.io.IOException;

/**
 * A camera that follows a spatial and can turn around it by dragging the mouse
 * or with a joystick.
 *
 * @author Dustin
 */
public class ThirdPersonCamera implements ActionListener, AnalogListener, Control {

	// TODO Get they keys right!
	private Spatial target = null;
	private float minVerticalRotation = 0.00f;
	private float maxVerticalRotation = FastMath.PI / 2;
	private float minDistance = 1.0f;
	private float maxDistance = 40.0f;
	private float distance = 20;
	private float zoomSpeed = 2f;
	private float rotationSpeed = 1.0f;
	private float rotation = 0;
	private float trailingRotationInertia = 0.05f;
	private float zoomSensitivity = 5f;
	private float rotationSensitivity = 5f;
	private float chasingSensitivity = 5f;
	private float trailingSensitivity = 0.5f;
	private float vRotation = FastMath.PI / 6;
	private boolean smoothMotion = false;
	private boolean trailingEnabled = true;
	private float rotationLerpFactor = 0;
	private float trailingLerpFactor = 0;
	private boolean rotating = false;
	private boolean vRotating = false;
	private float targetRotation = rotation;
	private InputManager inputManager;
	private Vector3f initialUpVec;
	private float targetVRotation = vRotation;
	private float vRotationLerpFactor = 0;
	private float targetDistance = distance;
	private float distanceLerpFactor = 0;
	private boolean zooming = false;
	private boolean trailing = false;
	private boolean chasing = false;
	private boolean canRotate;
	private float offsetDistance = 0.002f;
	private Vector3f prevPos;
	private boolean targetMoves = false;
	private boolean enabled = true;
	private Camera cam = null;
	private final Vector3f targetDir = new Vector3f();
	private float previousTargetRotation;
	private final Vector3f pos = new Vector3f();
	protected Vector3f targetLocation = new Vector3f(0, 0, 0);
	protected boolean dragToRotate = true;
	protected Vector3f lookAtOffset = new Vector3f(0, 0, 0);
	protected boolean leftClickRotate = true;
	protected boolean rightClickRotate = true;
	private Vector3f temp = new Vector3f(0, 0, 0);
	protected boolean invertYaxis = false;
	protected boolean invertXaxis = false;
	private final static String MouseDown = "MouseDown";
	private final static String MouseUp = "MouseUp";
	private final static String MouseZoomIn = "MouseZoomIn";
	private final static String MouseZoomOut = "MouseZoomOut";
	private final static String MouseLeft = "MouseLeft";
	private final static String MouseRight = "MouseRight";
	private final static String MouseToggleRotate = "MouseToggleRotate";
	private final static String JoystickDown = "JoystickDown";
	private final static String JoystickUp = "JoystickUp";
	private final static String JoystickZoomIn = "JoystickZoomIn";
	private final static String JoystickZoomOut = "JoystickZoomOut";
	private final static String JoystickLeft = "JoystickLeft";
	private final static String JoystickRight = "JoystickRight";
	private final static String KeyboardDown = "KeyboardDown";
	private final static String KeyboardUp = "KeyboardUp";
	private final static String KeyboardZoomIn = "KeyboardZoomIn"; //I
	private final static String KeyboardZoomOut = "KeyboardZoomOut"; //O
	private final static String KeyboardLeft = "KeyboardLeft";
	private final static String KeyboardRight = "KeyboardRight";

	/**
	 * Constructs the chase camera
	 *
	 * @param cam the application camera
	 * @param target the spatial to follow
	 */
	public ThirdPersonCamera(Camera cam, final Spatial target) {
		this(cam);
		target.addControl(this);
	}

	/**
	 * Constructs the chase camera if you value this constructor you have to
	 * attach the cam later to a spatial doing spatial.addControl(chaseCamera);
	 *
	 * @param cam the application camera
	 */
	public ThirdPersonCamera(Camera cam) {
		this.cam = cam;
		initialUpVec = cam.getUp().clone();
	}

	/**
	 * Constructs the chase camera, and registers inputs if you value this
	 * constructor you have to attach the cam later to a spatial doing
	 * spatial.addControl(chaseCamera);
	 *
	 * @param cam the application camera
	 * @param inputManager the inputManager of the application to register
	 * inputs
	 */
	public ThirdPersonCamera(Camera cam, InputManager inputManager) {
		this(cam);
		registerWithInput(inputManager);
	}

	/**
	 * Constructs the chase camera, and registers inputs
	 *
	 * @param cam the application camera
	 * @param target the spatial to follow
	 * @param inputManager the inputManager of the application to register
	 * inputs
	 */
	public ThirdPersonCamera(Camera cam, final Spatial target, InputManager inputManager) {
		this(cam, target);
		registerWithInput(inputManager);
	}

	/**
	 *
	 * @param name
	 * @param isPressed
	 * @param tpf
	 */
	@Override
	public void onAction(String name, boolean isPressed, float tpf) {
		if (isPressed) {
			switch (name) {
				case KeyboardLeft:
					horizontalRotate(-tpf * 50);
					break;
				case KeyboardRight:
					horizontalRotate(tpf * 50);
					break;
				case KeyboardUp:
					verticalRotate(tpf * 50);
					break;
				case KeyboardDown:
					verticalRotate(-tpf * 50);
					break;
				case KeyboardZoomIn:
					zoomCamera(-tpf * 100);
					if (!zoomin) {
						distanceLerpFactor = 0;
					}
					zoomin = true;
					break;
				case KeyboardZoomOut:
					zoomCamera(+tpf * 100);
					if (zoomin) {
						distanceLerpFactor = 0;
					}
					zoomin = false;
					break;
				case MouseToggleRotate:
					canRotate = true;
					inputManager.setCursorVisible(false);
					break;
			}
		} else {
			switch (name) {
				case MouseToggleRotate:
					canRotate = false;
					inputManager.setCursorVisible(true);
					break;
			}
		}
	}
	private boolean zoomin;

	/**
	 *
	 * @param name
	 * @param value
	 * @param tpf
	 */
	@Override
	public void onAnalog(String name, float value, float tpf) {
		switch (name) {
			case JoystickLeft:
				horizontalRotate(-value);
				break;
			case JoystickRight:
				horizontalRotate(value);
				break;
			case JoystickUp:
				verticalRotate(value);
				break;
			case JoystickDown:
				verticalRotate(-value);
				break;
			case JoystickZoomIn:
				zoomCamera(-value);
				if (!zoomin) {
					distanceLerpFactor = 0;
				}
				zoomin = true;
				break;
			case MouseZoomIn:
				if (enabled) {
					zoomCamera(-value);
				}
				if (!zoomin) {
					distanceLerpFactor = 0;
				}
				zoomin = true;
				break;
			case JoystickZoomOut:
				zoomCamera(+value);
				if (zoomin) {
					distanceLerpFactor = 0;
				}
				zoomin = false;
				break;
			case MouseZoomOut:
				if (enabled) {
					zoomCamera(+value);
				}
				if (zoomin) {
					distanceLerpFactor = 0;
				}
				zoomin = false;
				break;
			default:
				if (enabled && canRotate) {
					switch (name) {
						case MouseLeft:
							horizontalRotate(-value);
							break;
						case MouseRight:
							horizontalRotate(value);
							break;
						case MouseUp:
							verticalRotate(value);
							break;
						case MouseDown:
							verticalRotate(-value);
							break;
					}
				}
				break;
		}
	}

	/**
	 * Registers inputs with the input manager
	 *
	 * @param inputManager
	 */
	public final void registerWithInput(InputManager inputManager) {
		int gamepad = 0;
		int y = 3;
		int x = 2;
		//int rz = 1;
		//int z = 0;
		this.inputManager = inputManager;
		if (!invertYaxis) {
			inputManager.addMapping(MouseDown, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
			inputManager.addMapping(JoystickDown, new JoyAxisTrigger(gamepad, y, true)); //up on right stick
			inputManager.addMapping(KeyboardDown, new KeyTrigger(KeyInput.KEY_DOWN));
			inputManager.addMapping(MouseUp, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
			inputManager.addMapping(JoystickUp, new JoyAxisTrigger(gamepad, y, false)); //down on right stick
			inputManager.addMapping(KeyboardUp, new KeyTrigger(KeyInput.KEY_UP));
		} else {
			inputManager.addMapping(MouseDown, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
			inputManager.addMapping(JoystickDown, new JoyAxisTrigger(gamepad, y, false)); //down on right stick
			inputManager.addMapping(KeyboardDown, new KeyTrigger(KeyInput.KEY_UP));
			inputManager.addMapping(MouseUp, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
			inputManager.addMapping(JoystickUp, new JoyAxisTrigger(gamepad, y, true)); //up on right stick
			inputManager.addMapping(KeyboardUp, new KeyTrigger(KeyInput.KEY_DOWN));
		}
		// TODO joystick zoom
		inputManager.addMapping(MouseZoomIn, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
		inputManager.addMapping(KeyboardZoomIn, new KeyTrigger(KeyInput.KEY_I));
		inputManager.addMapping(MouseZoomOut, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
		inputManager.addMapping(KeyboardZoomOut, new KeyTrigger(KeyInput.KEY_O));
		if (!invertXaxis) {
			inputManager.addMapping(MouseLeft, new MouseAxisTrigger(MouseInput.AXIS_X, true));
			inputManager.addMapping(JoystickLeft, new JoyAxisTrigger(gamepad, x, true)); //left on right stick
			inputManager.addMapping(KeyboardLeft, new KeyTrigger(KeyInput.KEY_LEFT));
			inputManager.addMapping(MouseRight, new MouseAxisTrigger(MouseInput.AXIS_X, false));
			inputManager.addMapping(JoystickRight, new JoyAxisTrigger(gamepad, x, false)); //right on right stick
			inputManager.addMapping(KeyboardRight, new KeyTrigger(KeyInput.KEY_RIGHT));
		} else {
			inputManager.addMapping(MouseLeft, new MouseAxisTrigger(MouseInput.AXIS_X, false));
			inputManager.addMapping(JoystickLeft, new JoyAxisTrigger(gamepad, x, false)); //left on right stick
			inputManager.addMapping(KeyboardLeft, new KeyTrigger(KeyInput.KEY_RIGHT));
			inputManager.addMapping(MouseRight, new MouseAxisTrigger(MouseInput.AXIS_X, true));
			inputManager.addMapping(JoystickRight, new JoyAxisTrigger(gamepad, x, true)); //right on right stick
			inputManager.addMapping(KeyboardRight, new KeyTrigger(KeyInput.KEY_LEFT));
		}
		// no toggle for joystick
		inputManager.addMapping(MouseToggleRotate, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
		inputManager.addMapping(MouseToggleRotate, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
		// add listeners
		inputManager.addListener(this,
				MouseToggleRotate,
				MouseDown,
				MouseUp,
				MouseLeft,
				MouseRight,
				MouseZoomIn,
				MouseZoomOut,
				JoystickDown,
				JoystickUp,
				JoystickLeft,
				JoystickRight,
				JoystickZoomIn,
				JoystickZoomOut,
				KeyboardUp,
				KeyboardLeft,
				KeyboardDown,
				KeyboardRight,
				KeyboardZoomIn,
				KeyboardZoomOut);
	}

	/**
	 * Sets custom triggers for toggleing the rotation of the cam deafult are
	 * new MouseButtonTrigger(MouseInput.BUTTON_LEFT) left mouse button new
	 * MouseButtonTrigger(MouseInput.BUTTON_RIGHT) right mouse button
	 *
	 * @param triggers
	 */
	public void setToggleRotationTrigger(Trigger... triggers) {
		inputManager.deleteMapping(MouseToggleRotate);
		inputManager.addMapping(MouseToggleRotate, triggers);
		inputManager.addListener(this, MouseToggleRotate);
	}

	/**
	 * Sets custom triggers for zomming in the cam default is new
	 * MouseAxisTrigger(MouseInput.AXIS_WHEEL, true) mouse wheel up
	 *
	 * @param triggers
	 */
	public void setZoomInTrigger(Trigger... triggers) {
		inputManager.deleteMapping(MouseZoomIn);
		inputManager.addMapping(MouseZoomIn, triggers);
		inputManager.addListener(this, MouseZoomIn);
	}

	/**
	 * Sets custom triggers for zomming out the cam default is new
	 * MouseAxisTrigger(MouseInput.AXIS_WHEEL, false) mouse wheel down
	 *
	 * @param triggers
	 */
	public void setZoomOutTrigger(Trigger... triggers) {
		inputManager.deleteMapping(MouseZoomOut);
		inputManager.addMapping(MouseZoomOut, triggers);
		inputManager.addListener(this, MouseZoomOut);
	}

	/**
	 *
	 */
	private void computePosition() {
		float hDistance = (distance) * FastMath.sin((FastMath.PI / 2) - vRotation);
		pos.set(hDistance * FastMath.cos(rotation), (distance) * FastMath.sin(vRotation), hDistance * FastMath.sin(rotation));
		pos.addLocal(target.getWorldTranslation());
	}

	/**
	 * rotate the camera around the target on the horizontal (left/right X)
	 * plane
	 *
	 * @param value
	 */
	public void horizontalRotate(float value) {
		rotating = true;
		targetRotation += value * rotationSpeed;
	}

	/**
	 * move the camera toward or away the target
	 *
	 * @param value
	 */
	public void zoomCamera(float value) {
		zooming = true;
		targetDistance += value * zoomSpeed;
		if (targetDistance > maxDistance) {
			targetDistance = maxDistance;
		}
		if (targetDistance < minDistance) {
			targetDistance = minDistance;
		}
		if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) {
			targetVRotation = minVerticalRotation;
		}
	}

	/**
	 * rotate the camera around the target on the vertical (up/down - Z) plane
	 *
	 * @param value
	 */
	public void verticalRotate(float value) {
		vRotating = true;
		targetVRotation += value * rotationSpeed;
		if (targetVRotation > maxVerticalRotation) {
			targetVRotation = maxVerticalRotation;
		}
		if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) {
			targetVRotation = minVerticalRotation;
		}
	}

	/**
	 * Updates the camera, should only be called internally
	 *
	 * @param tpf
	 */
	protected void updateCamera(float tpf) {
		if (enabled) {
			targetLocation.set(target.getWorldTranslation()).addLocal(lookAtOffset);
			if (smoothMotion) {
				//computation of target direction
				targetDir.set(targetLocation).subtractLocal(prevPos);
				float dist = targetDir.length();
				//Low pass filtering on the target postition to avoid shaking when physics are enabled.
				if (offsetDistance < dist) {
					//target moves, start chasing.
					chasing = true;
					//target moves, start trailing if it has to.
					if (trailingEnabled) {
						trailing = true;
					}
					//target moves...
					targetMoves = true;
				} else {
					//if target was moving, we compute a slight offset in rotation to avoid a rought stop of the cam
					//We do not if the player is rotationg the cam
					if (targetMoves && !canRotate) {
						if (targetRotation - rotation > trailingRotationInertia) {
							targetRotation = rotation + trailingRotationInertia;
						} else if (targetRotation - rotation < -trailingRotationInertia) {
							targetRotation = rotation - trailingRotationInertia;
						}
					}
					//Target stops
					targetMoves = false;
				}
				//the user is rotating the cam by dragging the mouse
				if (canRotate) {
					//reseting the trailing lerp factor
					trailingLerpFactor = 0;
					//stop trailing user has the control
					trailing = false;
				}
				if (trailingEnabled && trailing) {
					if (targetMoves) {
						//computation if the inverted direction of the target
						Vector3f a = targetDir.negate().normalizeLocal();
						//the x unit vector
						Vector3f b = Vector3f.UNIT_X;
						//2d is good enough
						a.y = 0;
						//computation of the rotation angle between the x axis and the trail
						if (targetDir.z > 0) {
							targetRotation = FastMath.TWO_PI - FastMath.acos(a.dot(b));
						} else {
							targetRotation = FastMath.acos(a.dot(b));
						}
						if (targetRotation - rotation > FastMath.PI || targetRotation - rotation < -FastMath.PI) {
							targetRotation -= FastMath.TWO_PI;
						}
						//if there is an important change in the direction while trailing reset of the lerp factor to avoid jumpy movements
						if (targetRotation != previousTargetRotation && FastMath.abs(targetRotation - previousTargetRotation) > FastMath.PI / 8) {
							trailingLerpFactor = 0;
						}
						previousTargetRotation = targetRotation;
					}
					//computing lerp factor
					trailingLerpFactor = Math.min(trailingLerpFactor + tpf * tpf * trailingSensitivity, 1);
					//computing rotation by linear interpolation
					rotation = FastMath.interpolateLinear(trailingLerpFactor, rotation, targetRotation);
					//if the rotation is near the target rotation we're good, that's over
					if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) {
						trailing = false;
						trailingLerpFactor = 0;
					}
				}
				//linear interpolation of the distance while chasing
				if (chasing) {
					distance = temp.set(targetLocation).subtractLocal(cam.getLocation()).length();
					distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * chasingSensitivity * 0.05f), 1);
					distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance);
					if (targetDistance + 0.01f >= distance && targetDistance - 0.01f <= distance) {
						distanceLerpFactor = 0;
						chasing = false;
					}
				}
				//linear interpolation of the distance while zooming
				if (zooming) {
					distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * zoomSensitivity), 1);
					distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance);
					if (targetDistance + 0.1f >= distance && targetDistance - 0.1f <= distance) {
						zooming = false;
						distanceLerpFactor = 0;
					}
				}
				//linear interpolation of the rotation while rotating horizontally
				if (rotating) {
					rotationLerpFactor = Math.min(rotationLerpFactor + tpf * tpf * rotationSensitivity, 1);
					rotation = FastMath.interpolateLinear(rotationLerpFactor, rotation, targetRotation);
					if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) {
						rotating = false;
						rotationLerpFactor = 0;
					}
				}
				//linear interpolation of the rotation while rotating vertically
				if (vRotating) {
					vRotationLerpFactor = Math.min(vRotationLerpFactor + tpf * tpf * rotationSensitivity, 1);
					vRotation = FastMath.interpolateLinear(vRotationLerpFactor, vRotation, targetVRotation);
					if (targetVRotation + 0.01f >= vRotation && targetVRotation - 0.01f <= vRotation) {
						vRotating = false;
						vRotationLerpFactor = 0;
					}
				}
				//computing the position
				computePosition();
				//setting the position at last
				cam.setLocation(pos.addLocal(lookAtOffset));
			} else {
				//easy no smooth motion
				vRotation = targetVRotation;
				rotation = targetRotation;
				distance = targetDistance;
				computePosition();
				cam.setLocation(pos.addLocal(lookAtOffset));
			}
			//keeping track on the previous position of the target
			prevPos.set(targetLocation);
			//the cam looks at the target
			cam.lookAt(targetLocation, initialUpVec);
		}
	}

	/**
	 * Return the enabled/disabled state of the camera
	 *
	 * @return true if the camera is enabled
	 */
	public boolean isEnabled() {
		return enabled;
	}

	/**
	 * Enable or disable the camera
	 *
	 * @param enabled true to enable
	 */
	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
		if (!enabled) {
			canRotate = false; // reset this flag in-case it was on before
		}
	}

	/**
	 * Returns the max zoom distance of the camera (default is 40)
	 *
	 * @return maxDistance
	 */
	public float getMaxDistance() {
		return maxDistance;
	}

	/**
	 * Sets the max zoom distance of the camera (default is 40)
	 *
	 * @param maxDistance
	 */
	public void setMaxDistance(float maxDistance) {
		this.maxDistance = maxDistance;
	}

	/**
	 * Returns the min zoom distance of the camera (default is 1)
	 *
	 * @return minDistance
	 */
	public float getMinDistance() {
		return minDistance;
	}

	/**
	 * Sets the min zoom distance of the camera (default is 1)
	 *
	 * @return minDistance
	 */
	public void setMinDistance(float minDistance) {
		this.minDistance = minDistance;
	}

	/**
	 * clone this camera for a spatial
	 *
	 * @param spatial
	 * @return
	 */
	@Override
	public Control cloneForSpatial(Spatial spatial) {
		ThirdPersonCamera cc = new ThirdPersonCamera(cam, spatial, inputManager);
		cc.setMaxDistance(getMaxDistance());
		cc.setMinDistance(getMinDistance());
		return cc;
	}

	/**
	 * Sets the spacial for the camera control, should only be used internally
	 *
	 * @param spatial
	 */
	@Override
	public void setSpatial(Spatial spatial) {
		target = spatial;
		if (spatial == null) {
			return;
		}
		computePosition();
		prevPos = new Vector3f(target.getWorldTranslation());
		cam.setLocation(pos);
	}

	/**
	 * update the camera control, should on ly be used internally
	 *
	 * @param tpf
	 */
	@Override
	public void update(float tpf) {
		updateCamera(tpf);
	}

	/**
	 * renders the camera control, should on ly be used internally
	 *
	 * @param rm
	 * @param vp
	 */
	@Override
	public void render(RenderManager rm, ViewPort vp) {
		//nothing to render
	}

	/**
	 * Write the camera
	 *
	 * @param ex the exporter
	 * @throws IOException
	 */
	@Override
	public void write(JmeExporter ex) throws IOException {
		OutputCapsule capsule = ex.getCapsule(this);
		capsule.write(maxDistance, "maxDistance", 40);
		capsule.write(minDistance, "minDistance", 1);
	}

	/**
	 * Read the camera
	 *
	 * @param im
	 * @throws IOException
	 */
	@Override
	public void read(JmeImporter im) throws IOException {
		InputCapsule ic = im.getCapsule(this);
		maxDistance = ic.readFloat("maxDistance", 40);
		minDistance = ic.readFloat("minDistance", 1);
	}

	/**
	 * returns the maximal vertical rotation angle of the camera around the
	 * target
	 *
	 * @return
	 */
	public float getMaxVerticalRotation() {
		return maxVerticalRotation;
	}

	/**
	 * sets the maximal vertical rotation angle of the camera around the target
	 * default is Pi/2;
	 *
	 * @param maxVerticalRotation
	 */
	public void setMaxVerticalRotation(float maxVerticalRotation) {
		this.maxVerticalRotation = maxVerticalRotation;
	}

	/**
	 * returns the minimal vertical rotation angle of the camera around the
	 * target
	 *
	 * @return
	 */
	public float getMinVerticalRotation() {
		return minVerticalRotation;
	}

	/**
	 * sets the minimal vertical rotation angle of the camera around the target
	 * default is 0;
	 *
	 * @param minHeight
	 */
	public void setMinVerticalRotation(float minHeight) {
		this.minVerticalRotation = minHeight;
	}

	/**
	 * returns true is smmoth motion is enabled for this chase camera
	 *
	 * @return
	 */
	public boolean isSmoothMotion() {
		return smoothMotion;
	}

	/**
	 * Enables smooth motion for this chase camera
	 *
	 * @param smoothMotion
	 */
	public void setSmoothMotion(boolean smoothMotion) {
		this.smoothMotion = smoothMotion;
	}

	/**
	 * returns the chasing sensitivity
	 *
	 * @return
	 */
	public float getChasingSensitivity() {
		return chasingSensitivity;
	}

	/**
	 * Sets the chasing sensitivity, the lower the value the slower the camera
	 * will follow the target when it moves
	 *
	 * @param chasingSensitivity
	 */
	public void setChasingSensitivity(float chasingSensitivity) {
		this.chasingSensitivity = chasingSensitivity;
	}

	/**
	 * Returns the rotation sensitivity
	 *
	 * @return
	 */
	public float getRotationSensitivity() {
		return rotationSensitivity;
	}

	/**
	 * Sets the rotation sensitivity, the lower the value the slower the camera
	 * will rotates around the target when draging with the mouse default is 5
	 *
	 * @param rotationSensitivity
	 */
	public void setRotationSensitivity(float rotationSensitivity) {
		this.rotationSensitivity = rotationSensitivity;
	}

	/**
	 * returns true if the trailing is enabled
	 *
	 * @return
	 */
	public boolean isTrailingEnabled() {
		return trailingEnabled;
	}

	/**
	 * Enable the camera trailing : The camera smoothly go in the targets trail
	 * when it moves.
	 *
	 * @param trailingEnabled
	 */
	public void setTrailingEnabled(boolean trailingEnabled) {
		this.trailingEnabled = trailingEnabled;
	}

	/**
	 * returns the trailing rotation inertia
	 *
	 * @return
	 */
	public float getTrailingRotationInertia() {
		return trailingRotationInertia;
	}

	/**
	 * Sets the trailing rotation inertia : default is 0.1. This prevent the
	 * camera to roughtly stop when the target stops moving before the camera
	 * reached the trail position.
	 *
	 * @param trailingRotationInertia
	 */
	public void setTrailingRotationInertia(float trailingRotationInertia) {
		this.trailingRotationInertia = trailingRotationInertia;
	}

	/**
	 * returns the trailing sensitivity
	 *
	 * @return
	 */
	public float getTrailingSensitivity() {
		return trailingSensitivity;
	}

	/**
	 * Sets the trailing sensitivity, the lower the value, the slower the camera
	 * will go in the target trail when it moves. default is 0.5;
	 *
	 * @param trailingSensitivity
	 */
	public void setTrailingSensitivity(float trailingSensitivity) {
		this.trailingSensitivity = trailingSensitivity;
	}

	/**
	 * returns the zoom sensitivity
	 *
	 * @return
	 */
	public float getZoomSensitivity() {
		return zoomSensitivity;
	}

	/**
	 * Sets the zoom sensitivity, the lower the value, the slower the camera
	 * will zoom in and out. default is 5.
	 *
	 * @param zoomSensitivity
	 */
	public void setZoomSensitivity(float zoomSensitivity) {
		this.zoomSensitivity = zoomSensitivity;
	}

	/**
	 * Sets the default distance at start of applicaiton
	 *
	 * @param defaultDistance
	 */
	public void setDefaultDistance(float defaultDistance) {
		distance = defaultDistance;
		targetDistance = distance;
	}

	/**
	 * sets the default horizontal rotation of the camera at start of the
	 * application
	 *
	 * @param angle
	 */
	public void setDefaultHorizontalRotation(float angle) {
		rotation = angle;
		targetRotation = angle;
	}

	/**
	 * sets the default vertical rotation of the camera at start of the
	 * application
	 *
	 * @param angle
	 */
	public void setDefaultVerticalRotation(float angle) {
		vRotation = angle;
		targetVRotation = angle;
	}

	/**
	 * @return If drag to rotate feature is enabled.
	 *
	 * @see FlyByCamera#setDragToRotate(boolean)
	 */
	public boolean isDragToRotate() {
		return dragToRotate;
	}

	/**
	 * @param dragToRotate When true, the user must hold the mouse button and
	 * drag over the screen to rotate the camera, and the cursor is visible
	 * until dragged. Otherwise, the cursor is invisible at all times and
	 * holding the mouse button is not needed to rotate the camera. This feature
	 * is disabled by default.
	 */
	public void setDragToRotate(boolean dragToRotate) {
		this.dragToRotate = dragToRotate;
		this.canRotate = !dragToRotate;
		inputManager.setCursorVisible(dragToRotate);
	}

	/**
	 * return the current distance from the camera to the target
	 *
	 * @return
	 */
	public float getDistanceToTarget() {
		return distance;
	}

	/**
	 * returns the current horizontal rotation around the target in radians
	 *
	 * @return
	 */
	public float getHorizontalRotation() {
		return rotation;
	}

	/**
	 * returns the current vertical rotation around the target in radians.
	 *
	 * @return
	 */
	public float getVerticalRotation() {
		return vRotation;
	}

	/**
	 * returns the offset from the target's position where the camera looks at
	 *
	 * @return
	 */
	public Vector3f getLookAtOffset() {
		return lookAtOffset;
	}

	/**
	 * Sets the offset from the target's position where the camera looks at
	 *
	 * @param lookAtOffset
	 */
	public void setLookAtOffset(Vector3f lookAtOffset) {
		this.lookAtOffset = lookAtOffset;
	}

	/**
	 * invert the vertical axis movement of the mouse
	 *
	 * @param invertYaxis
	 */
	public void setInvertVerticalAxis(boolean invertYaxis) {
		this.invertYaxis = invertYaxis;
		inputManager.deleteMapping(MouseDown);
		inputManager.deleteMapping(MouseUp);
		if (!invertYaxis) {
			inputManager.addMapping(MouseDown, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
			inputManager.addMapping(MouseUp, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
		} else {
			inputManager.addMapping(MouseDown, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
			inputManager.addMapping(MouseUp, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
		}
		inputManager.addListener(this, MouseDown, MouseUp);
	}

	/**
	 * invert the Horizontal axis movement of the mouse
	 *
	 * @param invertYaxis
	 */
	public void setInvertHorizontalAxis(boolean invertXaxis) {
		this.invertXaxis = invertXaxis;
		inputManager.deleteMapping(MouseLeft);
		inputManager.deleteMapping(MouseRight);
		if (!invertXaxis) {
			inputManager.addMapping(MouseLeft, new MouseAxisTrigger(MouseInput.AXIS_X, true));
			inputManager.addMapping(MouseRight, new MouseAxisTrigger(MouseInput.AXIS_X, false));
		} else {
			inputManager.addMapping(MouseLeft, new MouseAxisTrigger(MouseInput.AXIS_X, false));
			inputManager.addMapping(MouseRight, new MouseAxisTrigger(MouseInput.AXIS_X, true));
		}
		inputManager.addListener(this, MouseLeft, MouseRight);
	}
}